﻿using System;
using System.Globalization;
using System.Numerics;
using System.Text;
using ParserGenerator.CharacterMapping;
using ParserGenerator.Exceptions;
using ParserGenerator.Extensions;
using ParserGenerator.Input;
using ParserGenerator.Output;

namespace ParserGenerator.Languages.JSON
{
    partial class JSONParser
    {
        #region tokenizer semantic code
        private readonly StringBuilder _sb = new StringBuilder();
        private SourceContext _context;

        private void BeginString(int start)
        {
            _sb.Clear();
            _context = TokenContext;
            PushStart(start);
        }

        private void AppendTokenString()
        {
            string str = TokenContext.GetSource();
            _sb.Append(str);
        }

        private void AppendChar(char value)
        {
            _sb.Append(value);
        }

        private void AppendEscape(int n, bool isOctal)
        {
            string str = TokenContext.GetSource();
            if (n == -1)
                n = str.Length - (isOctal ? 1 : 2);
            BigInteger num = 0;
            if (isOctal)
            {
                for (int i = 0; i < n; i++)
                {
                    int ch = str[1 + i];
                    int d = ch.ValueOfDigit();
                    num = (num << 3) | d;
                }
            }
            else
            {
                for (int i = 0; i < n; i++)
                {
                    int ch = str[2 + i];
                    int d = ch.ValueOfDigit();
                    num = (num << 4) | d;
                }
            }
            if (num < CharExtensions.MinChar || num > CharExtensions.MaxChar)
            {
                string message = string.Format(SR.CharMustBeInRange, CharInterval.Universal);
                throw SyntaxError(message);
            }
            int v = (int)num;
            _sb.Append(char.ConvertFromUtf32(v));
        }

        private void EndString()
        {
            TokenContext = new SourceContext(_context.Begin, TokenContext.End);
            TokenValue = _sb.ToString();
            PopStart();
        }

        private SyntaxErrorException UnterminatedStringLiteral()
        {
            TokenContext = new SourceContext(_context.Begin, TokenContext.End);
            return SyntaxError(SR.UnterminatedStringLiteral, TokenContext);
        }

        private void ScanIntegerLiteral()
        {
            string str = TokenContext.GetSource();
            TokenValue = BigInteger.Parse(str);
        }

        private void ScanFloatLiteral()
        {
            string str = TokenContext.GetSource();
            TokenValue = double.Parse(str);
        }
        #endregion
    }

    partial class SyntaxTreeNodeWriter
    {
        #region JSON writer
        public virtual void WriteNullJsonValue(NullJsonValue n, IndentedWriter writer)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            writer.Write("null");
        }

        public virtual void WriteBooleanJsonValue(BooleanJsonValue n, IndentedWriter writer)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            writer.Write(n.Value ? "true" : "false");
        }

        public virtual string FormatIntegerLiteral(BigInteger value)
        {
            return value.ToString(CultureInfo.InvariantCulture);
        }

        public virtual void WriteIntegerJsonValue(IntegerJsonValue n, IndentedWriter writer)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            writer.Write(FormatIntegerLiteral(n.Value));
        }

        public virtual string FormatFloatLiteral(double value)
        {
            if (double.IsNaN(value))
                return "nan";
            else if (double.IsPositiveInfinity(value))
                return "inf";
            else if (double.IsNegativeInfinity(value))
                return "-inf";
            else
            {
                string str = value.ToString(CultureInfo.InvariantCulture).ToLowerInvariant();
                str = str.Replace("e+", "e");
                if (str.IndexOfAny(new[] { '.', 'e' }) == -1)
                    str += ".0";
                return str;
            }
        }

        public virtual void WriteFloatJsonValue(FloatJsonValue n, IndentedWriter writer)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            writer.Write(FormatFloatLiteral(n.Value));
        }

        public virtual string EscapeCharacter(int c, char delimiter)
        {
            if (c < CharExtensions.MinChar || c > CharExtensions.MaxChar)
                throw new ArgumentOutOfRangeException(nameof(c), SR.InvalidUnicodeCodepoint);
            if (delimiter != '\'' && delimiter != '"')
                throw new ArgumentException(SR.DelimiterMustBeSingleOrDoubleQuote, nameof(delimiter));
            switch (c)
            {
                case '\a': return "\\a";
                case '\b': return "\\b";
                case '\t': return "\\t";
                case '\n': return "\\n";
                case '\v': return "\\v";
                case '\f': return "\\f";
                case '\r': return "\\r";
                case '\\': return "\\\\";
                case '\'': return delimiter == '\'' ? "\\'" : "'";
                case '"': return delimiter == '"' ? "\\\"" : "\"";
                default:
                    if (' ' <= c && c <= 0x7E)
                        return "" + (char)c;
                    else if (0 <= c && c <= 0xFFFF)
                        return string.Format("\\u{0:X4}", c);
                    else
                        return string.Format("\\U{0:X8}", c);
            }
        }

        public virtual string FormatStringLiteral(string value)
        {
            if (value == null)
                throw new ArgumentNullException(nameof(value));
            const char delimiter = '"';
            StringBuilder sb = new StringBuilder();
            sb.Append(delimiter);
            foreach (int c in value.EnumCodepoints())
            {
                string str = EscapeCharacter(c, delimiter);
                sb.Append(str);
            }
            sb.Append(delimiter);
            return sb.ToString();
        }

        public virtual void WriteStringJsonValue(StringJsonValue n, IndentedWriter writer)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            writer.Write(FormatStringLiteral(n.Value));
        }

        public virtual void WriteArrayJsonValue(ArrayJsonValue n, IndentedWriter writer)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            if (n.Value.Count <= 8)
            {
                writer.Write("[");
                for (int i = 0; i < n.Value.Count; i++)
                {
                    if (i > 0)
                        writer.Write(", ");
                    WriteJsonValue(n.Value[i], writer);
                }
                writer.Write("]");
            }
            else
            {
                writer.OpenBracketBlock();
                for (int i = 0; i < n.Value.Count; i++)
                {
                    WriteJsonValue(n.Value[i], writer);
                    if (i < n.Value.Count - 1)
                        writer.Write(",");
                    writer.Write("\n");
                }
                writer.CloseBracketBlock();
            }
        }

        public virtual void WriteObjectJsonValue(ObjectJsonValue n, IndentedWriter writer)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            if (n.Value.Count <= 4)
            {
                writer.Write("{");
                for (int i = 0; i < n.Value.Count; i++)
                {
                    if (i > 0)
                        writer.Write(", ");
                    var t = n.Value[i];
                    writer.Write(FormatStringLiteral(t.Item1));
                    writer.Write(" : ");
                    WriteJsonValue(t.Item2, writer);
                }
                writer.Write("}");
            }
            else
            {
                writer.OpenCurlyBlock();
                for (int i = 0; i < n.Value.Count; i++)
                {
                    var t = n.Value[i];
                    writer.Write(FormatStringLiteral(t.Item1));
                    writer.Write(" : ");
                    WriteJsonValue(t.Item2, writer);
                    if (i < n.Value.Count - 1)
                        writer.Write(",");
                    writer.Write("\n");
                }
                writer.CloseCurlyBlock();
            }
        }
        #endregion
    }
}
